﻿using System;
using System.ComponentModel;
using System.ServiceModel;
using System.Diagnostics;

namespace NASA.BeAMartian.Extender
{
    public class ClientBaseExecutionClass<TChannel, TArgs>
        where TChannel : class
        where TArgs : AsyncCompletedEventArgs
    {
        private Action tryExecute;

        public ClientBaseExecutionClass(
               ClientBase<TChannel> client,
               Action tryExecute,
               Action<EventHandler<TArgs>> onCompletedSubcribe,
               Action<EventHandler<TArgs>> onCompletedUnsubcribe,
               EventHandler<TArgs> onCompleted,
               int maxAttempts)
        {
            this.tryExecute = tryExecute;

            int attempts = 0;
            var serviceName = client.GetType().Name;

            EventHandler<TArgs> handler = null;

            handler = new EventHandler<TArgs>(delegate(object s, TArgs e)
            {
                if (this.IsCanceled)
                {
                    if (onCompleted != null)
                    {
                        TArgs args = (TArgs)Activator.CreateInstance(typeof(TArgs), new object[] { null }, e.Error, true, e.UserState);
                        onCompleted(s, args);
                    }

                    if (onCompletedUnsubcribe != null)
                        onCompletedUnsubcribe(handler);
                }
                else if (e.Error == null) // Everything is OK
                {
                    if (onCompleted != null)
                        onCompleted(s, e);

                    if (onCompletedUnsubcribe != null)
                        onCompletedUnsubcribe(handler);
                }
                else if (e.Error is TimeoutException || e.Error is CommunicationException)
                {
                    attempts++;

                    if (attempts >= maxAttempts) // Final timeout after n attempts
                    {
                        Debug.WriteLine("[{2}], Final Timeout/Communication exception occured in '{0}' service after {1} attempts.", serviceName, attempts, DateTime.Now);

                        if (onCompleted != null)
                            onCompleted(s, e);

                        if (onCompletedUnsubcribe != null)
                            onCompletedUnsubcribe(handler);

                        return;
                    }

                    // Local timeout
                    Debug.WriteLine("[{2}] Timeout/Communication exception occured in '{0}' service (attempt #{1}).", serviceName, attempts, DateTime.Now);
                    Debug.WriteLine("[{2}] Attempt #{0} to execute call to '{1}' service.", attempts + 1, serviceName, DateTime.Now);
                    tryExecute(); // Try again.
                }
                else
                {
                    attempts++;

                    Debug.WriteLine("[{2}], Error {3} occured in '{0}' service after {1} attempts.", serviceName, attempts, DateTime.Now, e.Error.GetType().Name);

                    if (onCompleted != null)
                        onCompleted(s, e);

                    if (onCompletedUnsubcribe != null)
                        onCompletedUnsubcribe(handler);
                }
            });

            onCompletedSubcribe(handler);

            Debug.WriteLine("[{2}] Attempt #{0} to execute call to '{1}' service.", attempts + 1, serviceName, DateTime.Now);
        }

        public ClientBaseExecutionClass<TChannel, TArgs> Run()
        {
            this.tryExecute();
            return this;
        }

        public bool IsCanceled { get; private set; }

        public void Cancel()
        {
            this.IsCanceled = true;
        }
    }
}
